/*
 * NPlot - A charting library for .NET
 * 
 * PlotSurface2D.cs
 * Copyright (C) 2003-2006 Matt Howlett and others.
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of NPlot nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

// #define DEBUG_BOUNDING_BOXES
using System;
using System.Drawing;
using System.Diagnostics;
using System.Collections;

namespace NPlot
{

	/// <summary>
	/// Implements the surface on which IDrawables are drawn. Is extended
	/// by Bitmap.PlotSurface2D, Windows.PlotSurface2D etc. TODO: better explanation.
	/// </summary>
	public class PlotSurface2D : IPlotSurface2D
	{

		/// <summary>
		/// Possible positions of the X axis.
		/// </summary>
		public enum XAxisPosition
		{
			/// <summary>
			/// X axis is on the top.
			/// </summary>
			Top = 1,
			//Center = 2,
			/// <summary>
			/// X axis is on the bottom.
			/// </summary>
			Bottom = 3,
		}


		/// <summary>
		/// Possible positions of the Y axis.
		/// </summary>
		public enum YAxisPosition
		{
			/// <summary>
			/// Y axis on the left.
			/// </summary>
			Left = 1,
			// Center
			/// <summary>
			/// Y axis on the right.
			/// </summary>
			Right = 3,
		}


		private System.Drawing.StringFormat titleDrawFormat_;

		private Font titleFont_;
		private string title_;
		private Brush titleBrush_;
		private int padding_;
		private Axis xAxis1_;
		private Axis yAxis1_;
		private Axis xAxis2_;
		private Axis yAxis2_;
		private PhysicalAxis pXAxis1Cache_;
		private PhysicalAxis pYAxis1Cache_;
		private PhysicalAxis pXAxis2Cache_;
		private PhysicalAxis pYAxis2Cache_;
		private bool autoScaleAutoGeneratedAxes_ = false;
		private bool autoScaleTitle_ = false;

		private object plotAreaBoundingBoxCache_;
		private object bbXAxis1Cache_;
		private object bbXAxis2Cache_;
		private object bbYAxis1Cache_;
		private object bbYAxis2Cache_;
		private object bbTitleCache_;

		private object plotBackColor_ = null;
		private System.Drawing.Bitmap plotBackImage_ = null;
		private IRectangleBrush plotBackBrush_ = null;

		private System.Collections.ArrayList drawables_;
		private System.Collections.ArrayList xAxisPositions_;
		private System.Collections.ArrayList yAxisPositions_;
        private System.Collections.ArrayList zPositions_;
        private System.Collections.SortedList ordering_;

        private System.Drawing.Drawing2D.SmoothingMode smoothingMode_;

		private ArrayList axesConstraints_ = null;

		private Legend legend_;


		/// <summary>
		/// The physical bounding box of the last drawn plot surface area is available here.
		/// </summary>
		public Rectangle PlotAreaBoundingBoxCache
		{
			get
			{
				if (plotAreaBoundingBoxCache_ == null) 
				{
					return Rectangle.Empty;
				}
				else
				{
					return (Rectangle)plotAreaBoundingBoxCache_;
				}
			}
		}

		/// <summary>
		/// Performs a hit test with the given point and returns information 
		/// about the object being hit.
		/// </summary>
		/// <param name="p">The point to test.</param>
		/// <returns></returns>
		public System.Collections.ArrayList HitTest(Point p)
		{

			System.Collections.ArrayList a = new System.Collections.ArrayList();

			// this is the case if PlotSurface has been cleared.
			if (bbXAxis1Cache_ == null)
			{
				return a;
			}
			else if (bbXAxis1Cache_ != null && ((Rectangle) bbXAxis1Cache_ ).Contains(p))
			{
				a.Add( this.xAxis1_ );
				return a;
			}
			else if (bbYAxis1Cache_ != null && ((Rectangle) bbYAxis1Cache_ ).Contains(p))
			{
				a.Add( this.yAxis1_ );
				return a;
			}
			else if (bbXAxis2Cache_ != null && ((Rectangle) bbXAxis2Cache_ ).Contains(p))
			{
				a.Add( this.xAxis2_ );
				return a;
			}
			else if (bbXAxis2Cache_ != null && ((Rectangle) bbYAxis2Cache_ ).Contains(p))
			{
				a.Add( this.yAxis2_ );
				return a;
			}
			else if (bbTitleCache_ != null && ((Rectangle) bbTitleCache_ ).Contains(p))
			{
				a.Add( this );
				return a;
			}
			else if (plotAreaBoundingBoxCache_ != null && ((Rectangle) plotAreaBoundingBoxCache_ ).Contains(p))
			{
				a.Add( this );
				return a;
			}

			return a;
		}


		/// <summary>
		/// The bottom abscissa axis.
		/// </summary>
		public Axis XAxis1
		{
			get
			{
				return xAxis1_;
			}
			set
			{
				xAxis1_ = value;
			}
		}


		/// <summary>
		/// The left ordinate axis.
		/// </summary>
		public Axis YAxis1
		{
			get
			{
				return yAxis1_;
			}
			set
			{
				yAxis1_ = value;
			}
		}


		/// <summary>
		/// The top abscissa axis.
		/// </summary>
		public Axis XAxis2
		{
			get
			{
				return xAxis2_;
			}
			set
			{
				xAxis2_ = value;
			}
		}


		/// <summary>
		/// The right ordinate axis.
		/// </summary>
		public Axis YAxis2
		{
			get
			{
				return yAxis2_;
			}
			set
			{
				yAxis2_ = value;
			}
		}


		/// <summary>
		/// The physical XAxis1 that was last drawn.
		/// </summary>
		public PhysicalAxis PhysicalXAxis1Cache
		{
			get
			{
				return pXAxis1Cache_;
			}
		}


		/// <summary>
		/// The physical YAxis1 that was last drawn.
		/// </summary>
		public PhysicalAxis PhysicalYAxis1Cache
		{
			get
			{
				return pYAxis1Cache_;
			}
		}


		/// <summary>
		/// The physical XAxis2 that was last drawn.
		/// </summary>
		public PhysicalAxis PhysicalXAxis2Cache
		{
			get
			{
				return pXAxis2Cache_;
			}
		}


		/// <summary>
		/// The physical YAxis2 that was last drawn.
		/// </summary>
		public PhysicalAxis PhysicalYAxis2Cache
		{
			get
			{
				return pYAxis2Cache_;
			}
		}



		/// <summary>
		/// The chart title.
		/// </summary>
		public string Title
		{
			get
			{
				return title_;
			}
			set
			{
				title_ = value;
			}
		}


		/// <summary>
		/// The plot title font.
		/// </summary>
		public Font TitleFont
		{
			get
			{
				return titleFont_;
			}
			set
			{
				titleFont_ = value;
			}
		}


		/// <summary>
		/// The distance in pixels to leave between of the edge of the bounding rectangle
		/// supplied to the Draw method, and the markings that make up the plot.
		/// </summary>
		public int Padding
		{
			get
			{
				return padding_;
			}
			set
			{
				padding_ = value;
			}
		}


		/// <summary>
		/// Sets the title to be drawn using a solid brush of this color.
		/// </summary>
		public Color TitleColor
		{
			set
			{
				titleBrush_ = new SolidBrush( value );
			}
		}


		/// <summary>
		/// The brush used for drawing the title.
		/// </summary>
		public Brush TitleBrush
		{
			get
			{
				return titleBrush_;
			}
			set
			{
				titleBrush_ = value;
			}
		}


		/// <summary>
		/// A color used to paint the plot background. Mutually exclusive with PlotBackImage and PlotBackBrush
		/// </summary>
		public System.Drawing.Color PlotBackColor
		{
			set
			{
				plotBackColor_ = value;
				plotBackBrush_ = null;
				plotBackImage_ = null;
			}
		}

		
		/// <summary>
		/// An imaged used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
		/// </summary>
		public System.Drawing.Bitmap PlotBackImage
		{
			set
			{
				plotBackImage_ = value;
				plotBackColor_ = null;
				plotBackBrush_ = null;
			}
		}


		/// <summary>
		/// A Rectangle brush used to paint the plot background. Mutually exclusive with PlotBackColor and PlotBackBrush
		/// </summary>
		public IRectangleBrush PlotBackBrush
		{
			set
			{
				plotBackBrush_ = value;
				plotBackColor_ = null;
				plotBackImage_ = null;
			}
		}


		/// <summary>
		/// Smoothing mode to use when drawing plots.
		/// </summary>
		public System.Drawing.Drawing2D.SmoothingMode SmoothingMode 
		{ 
			get
			{
				return smoothingMode_;
			}
			set
			{
				this.smoothingMode_ = value;
			}
		}


		private void Init()
		{
			drawables_ = new ArrayList();
			xAxisPositions_ = new ArrayList();
			yAxisPositions_ = new ArrayList();
            zPositions_ = new ArrayList();
            ordering_ = new SortedList();
            FontFamily fontFamily = new FontFamily("Arial");
			TitleFont = new Font(fontFamily, 14, FontStyle.Regular, GraphicsUnit.Pixel);
			padding_ = 10;
			title_ = "";
			autoScaleTitle_ = false;
			autoScaleAutoGeneratedAxes_ = false;
			xAxis1_ = null;
			xAxis2_ = null;
			yAxis1_ = null;
			yAxis2_ = null;
			pXAxis1Cache_ = null;
			pYAxis1Cache_ = null;
			pXAxis2Cache_ = null;
			pYAxis2Cache_ = null;
			titleBrush_ = new SolidBrush( Color.Black );
			plotBackColor_ = Color.White;
			
			this.legend_ = null;

			smoothingMode_ = System.Drawing.Drawing2D.SmoothingMode.None;

			axesConstraints_ = new ArrayList();
		}


		/// <summary>
		/// Default constructor.
		/// </summary>
		public PlotSurface2D()
		{
			// only create this once.
			titleDrawFormat_ = new StringFormat();
			titleDrawFormat_.Alignment = StringAlignment.Center;

			Init();
		}


		private float DetermineScaleFactor( int w, int h )
		{

			float diag = (float)Math.Sqrt( w*w +  h*h );
			float scaleFactor = (diag / 1400.0f)*2.4f;
			
			if ( scaleFactor > 1.0f )
			{
				return scaleFactor;
			}
			else
			{
				return 1.0f;
			}
		}


        /// <summary>
        /// Adds a drawable object to the plot surface with z-order 0. If the object is an IPlot,
        /// the PlotSurface2D axes will also be updated. 
        /// </summary>
        /// <param name="p">The IDrawable object to add to the plot surface.</param>
        public void Add(IDrawable p)
        {
            Add(p, 0);
        }


        /// <summary>
		/// Adds a drawable object to the plot surface. If the object is an IPlot, 
		/// the PlotSurface2D axes will also be updated.
		/// </summary>
		/// <param name="p">The IDrawable object to add to the plot surface.</param>
		/// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
		public void Add( IDrawable p, int zOrder )
		{
			Add( p, XAxisPosition.Bottom, YAxisPosition.Left, zOrder );
		}


        /// <summary>
        /// Adds a drawable object to the plot surface against the specified axes with
        /// z-order of 0. If the object is an IPlot, the PlotSurface2D axes will also
        /// be updated.
        /// </summary>
        /// <param name="p">the IDrawable object to add to the plot surface</param>
        /// <param name="xp">the x-axis to add the plot against.</param>
        /// <param name="yp">the y-axis to add the plot against.</param>
        public void Add(IDrawable p, XAxisPosition xp, YAxisPosition yp)
        {
            Add(p, xp, yp, 0);
        }
        
        
        /// <summary>
        /// Adds a drawable object to the plot surface against the specified axes. If
		/// the object is an IPlot, the PlotSurface2D axes will also be updated.
		/// </summary>
		/// <param name="p">the IDrawable object to add to the plot surface</param>
		/// <param name="xp">the x-axis to add the plot against.</param>
		/// <param name="yp">the y-axis to add the plot against.</param>
		/// <param name="zOrder">The z-ordering when drawing (objects with lower numbers are drawn first)</param>
		public void Add( IDrawable p, XAxisPosition xp, YAxisPosition yp, int zOrder )
		{
			drawables_.Add( p );
			xAxisPositions_.Add( xp );
			yAxisPositions_.Add( yp );
            zPositions_.Add((double)zOrder);
            // fraction is to make key unique. With 10 million plots at same z, this buggers up.. 
            double fraction = (double)(++uniqueCounter_)/10000000.0f; 
            ordering_.Add( (double)zOrder + fraction, drawables_.Count - 1 );
            
            // if p is just an IDrawable, then it can't affect the axes.
			if ( p is IPlot )
			{
				UpdateAxes( false );
			}

		}

        private int uniqueCounter_ = 0;


        private void UpdateAxes( bool recalculateAll )
		{
            if (drawables_.Count != xAxisPositions_.Count || drawables_.Count != yAxisPositions_.Count)
            {
                throw new NPlotException("plots and axis position arrays our of sync");
            }

            int position = 0;

            // if we're not recalculating axes using all iplots then set
            // position to last one in list.
            if (!recalculateAll)
            {
                position = drawables_.Count - 1;
                if (position < 0) position = 0;
            }

            if (recalculateAll)
            {
                this.xAxis1_ = null;
                this.yAxis1_ = null;
                this.xAxis2_ = null;
                this.yAxis2_ = null;
            }

            for (int i = position; i < drawables_.Count; ++i)
            {

                // only update axes if this drawable is an IPlot.
                if (!(drawables_[position] is IPlot))
                    continue;

                IPlot p = (IPlot)drawables_[position];
                XAxisPosition xap = (XAxisPosition)xAxisPositions_[position];
                YAxisPosition yap = (YAxisPosition)yAxisPositions_[position];

                if (xap == XAxisPosition.Bottom)
                {
                    if (this.xAxis1_ == null)
                    {
                        this.xAxis1_ = p.SuggestXAxis();
                        if (this.xAxis1_ != null)
                        {
                            this.xAxis1_.TicksAngle = -(float)Math.PI / 2.0f;
                        }
                    }
                    else
                    {
                        this.xAxis1_.LUB(p.SuggestXAxis());
                    }

                    if (this.xAxis1_ != null)
                    {
                        this.xAxis1_.MinPhysicalLargeTickStep = 50;

                        if (this.AutoScaleAutoGeneratedAxes)
                        {
                            this.xAxis1_.AutoScaleText = true;
                            this.xAxis1_.AutoScaleTicks = true;
                            this.xAxis1_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            this.xAxis1_.AutoScaleText = false;
                            this.xAxis1_.AutoScaleTicks = false;
                            this.xAxis1_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }

                if (xap == XAxisPosition.Top)
                {
                    if (this.xAxis2_ == null)
                    {
                        this.xAxis2_ = p.SuggestXAxis();
                        if (this.xAxis2_ != null)
                        {
                            this.xAxis2_.TicksAngle = (float)Math.PI / 2.0f;
                        }
                    }
                    else
                    {
                        this.xAxis2_.LUB(p.SuggestXAxis());
                    }

                    if (this.xAxis2_ != null)
                    {
                        this.xAxis2_.MinPhysicalLargeTickStep = 50;

                        if (this.AutoScaleAutoGeneratedAxes)
                        {
                            this.xAxis2_.AutoScaleText = true;
                            this.xAxis2_.AutoScaleTicks = true;
                            this.xAxis2_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            this.xAxis2_.AutoScaleText = false;
                            this.xAxis2_.AutoScaleTicks = false;
                            this.xAxis2_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }

                if (yap == YAxisPosition.Left)
                {
                    if (this.yAxis1_ == null)
                    {
                        this.yAxis1_ = p.SuggestYAxis();
                        if (this.yAxis1_ != null)
                        {
                            this.yAxis1_.TicksAngle = (float)Math.PI / 2.0f;
                        }
                    }
                    else
                    {
                        this.yAxis1_.LUB(p.SuggestYAxis());
                    }

                    if (this.yAxis1_ != null)
                    {
                        if (this.AutoScaleAutoGeneratedAxes)
                        {
                            this.yAxis1_.AutoScaleText = true;
                            this.yAxis1_.AutoScaleTicks = true;
                            this.yAxis1_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            this.yAxis1_.AutoScaleText = false;
                            this.yAxis1_.AutoScaleTicks = false;
                            this.yAxis1_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }
                }

                if (yap == YAxisPosition.Right)
                {
                    if (this.yAxis2_ == null)
                    {
                        this.yAxis2_ = p.SuggestYAxis();
                        if (this.yAxis2_ != null)
                        {
                            this.yAxis2_.TicksAngle = -(float)Math.PI / 2.0f;
                        }
                    }
                    else
                    {
                        this.yAxis2_.LUB(p.SuggestYAxis());
                    }

                    if (this.yAxis2_ != null)
                    {
                        if (this.AutoScaleAutoGeneratedAxes)
                        {
                            this.yAxis2_.AutoScaleText = true;
                            this.yAxis2_.AutoScaleTicks = true;
                            this.yAxis2_.TicksIndependentOfPhysicalExtent = true;
                        }
                        else
                        {
                            this.yAxis2_.AutoScaleText = false;
                            this.yAxis2_.AutoScaleTicks = false;
                            this.yAxis2_.TicksIndependentOfPhysicalExtent = false;
                        }
                    }

                }
            }

        }


		private void DetermineAxesToDraw( out Axis xAxis1, out Axis xAxis2, out Axis yAxis1, out Axis yAxis2 )
		{
			xAxis1 = this.xAxis1_;
			xAxis2 = this.xAxis2_;
			yAxis1 = this.yAxis1_;
			yAxis2 = this.yAxis2_;

			if (this.xAxis1_ == null)
			{
				if (this.xAxis2_ == null)
				{
					throw new NPlotException( "Error: No X-Axis specified" );
				}
				xAxis1 = (Axis)this.xAxis2_.Clone();
				xAxis1.HideTickText = true;
				xAxis1.TicksAngle = -(float)Math.PI / 2.0f;
			}

			if (this.xAxis2_ == null)
			{
				// don't need to check if xAxis1_ == null, as case already handled above.
				xAxis2 = (Axis)this.xAxis1_.Clone();
				xAxis2.HideTickText = true;
				xAxis2.TicksAngle = (float)Math.PI / 2.0f;
			}

			if (this.yAxis1_ == null)
			{
				if (this.yAxis2_ == null)
				{
					throw new NPlotException( "Error: No Y-Axis specified" );
				}
				yAxis1 = (Axis)this.yAxis2_.Clone();
				yAxis1.HideTickText = true;
				yAxis1.TicksAngle = (float)Math.PI / 2.0f;
			}

			if (this.yAxis2_ == null)
			{
				// don't need to check if yAxis1_ == null, as case already handled above.
				yAxis2 = (Axis)this.yAxis1_.Clone();
				yAxis2.HideTickText = true;
				yAxis2.TicksAngle = -(float)Math.PI / 2.0f;
			}

		}


		private void DeterminePhysicalAxesToDraw( Rectangle bounds, 
			Axis xAxis1, Axis xAxis2, Axis yAxis1, Axis yAxis2,
			out PhysicalAxis pXAxis1, out PhysicalAxis pXAxis2, 
			out PhysicalAxis pYAxis1, out PhysicalAxis pYAxis2 )
		{

			System.Drawing.Rectangle cb = bounds;

			pXAxis1 = new PhysicalAxis( xAxis1,
				new Point( cb.Left, cb.Bottom ), new Point( cb.Right, cb.Bottom ) );
			pYAxis1 = new PhysicalAxis( yAxis1,
				new Point( cb.Left, cb.Bottom ), new Point( cb.Left, cb.Top ) );
			pXAxis2 = new PhysicalAxis( xAxis2,
				new Point( cb.Left, cb.Top), new Point( cb.Right, cb.Top) );
			pYAxis2 = new PhysicalAxis( yAxis2,
				new Point( cb.Right, cb.Bottom ), new Point( cb.Right, cb.Top ) );

			int bottomIndent = padding_;
			if (!pXAxis1.Axis.Hidden) 
			{
				// evaluate its bounding box
				Rectangle bb = pXAxis1.GetBoundingBox();
				// finally determine its indentation from the bottom
				bottomIndent = bottomIndent + bb.Bottom - cb.Bottom;
			}

			int leftIndent = padding_;
			if (!pYAxis1.Axis.Hidden) 
			{
				// evaluate its bounding box
				Rectangle bb = pYAxis1.GetBoundingBox();
				// finally determine its indentation from the left
				leftIndent = leftIndent - bb.Left + cb.Left;
			}

			int topIndent = padding_;
			float scale = this.DetermineScaleFactor( bounds.Width, bounds.Height );
			int titleHeight;
			if (this.AutoScaleTitle)
			{
				titleHeight = Utils.ScaleFont(titleFont_, scale).Height;
			}
			else
			{
				titleHeight = titleFont_.Height;
			}

			//count number of new lines in title.
			int nlCount = 0;
			for (int i=0; i<title_.Length; ++i)
			{
				if (title_[i] == '\n')
					nlCount += 1;
			}
			titleHeight = (int)( ((float)nlCount*0.75 + 1.0f) * (float)titleHeight);

			if (!pXAxis2.Axis.Hidden)  
			{
				// evaluate its bounding box
				Rectangle bb = pXAxis2.GetBoundingBox();
				topIndent = topIndent - bb.Top + cb.Top;

				// finally determine its indentation from the top
				// correct top indendation to take into account plot title
				if (title_ != "" )
				{
					topIndent += (int)(titleHeight * 1.3f);
				}
			}

			int rightIndent = padding_;
			if (!pYAxis2.Axis.Hidden) 
			{
				// evaluate its bounding box
				Rectangle bb = pYAxis2.GetBoundingBox();

				// finally determine its indentation from the right
				rightIndent = (int)(rightIndent + bb.Right-cb.Right);
			}

			// now we have all the default calculated positions and we can proceed to
			// "move" the axes to their right places

			// primary axes (bottom, left)
			pXAxis1.PhysicalMin = new Point( cb.Left+leftIndent, cb.Bottom-bottomIndent );
			pXAxis1.PhysicalMax = new Point( cb.Right-rightIndent, cb.Bottom-bottomIndent );
			pYAxis1.PhysicalMin = new Point( cb.Left+leftIndent, cb.Bottom-bottomIndent );
			pYAxis1.PhysicalMax = new Point( cb.Left+leftIndent, cb.Top+topIndent );

			// secondary axes (top, right)
			pXAxis2.PhysicalMin = new Point( cb.Left+leftIndent, cb.Top+topIndent );
			pXAxis2.PhysicalMax = new Point( cb.Right-rightIndent, cb.Top+topIndent );
			pYAxis2.PhysicalMin = new Point( cb.Right-rightIndent, cb.Bottom-bottomIndent );
			pYAxis2.PhysicalMax = new Point( cb.Right-rightIndent, cb.Top+topIndent );

		}



		/// <summary>
		/// Draw the the PlotSurface2D and all contents [axes, drawables, and legend] on the 
		/// supplied graphics surface.
		/// </summary>
		/// <param name="g">The graphics surface on which to draw.</param>
		/// <param name="bounds">A bounding box on this surface that denotes the area on the
		/// surface to confine drawing to.</param>
		public void Draw( Graphics g, Rectangle bounds )
		{
			// determine font sizes and tick scale factor.
			float scale = DetermineScaleFactor( bounds.Width, bounds.Height );

			// if there is nothing to plot, return.
			if ( drawables_.Count == 0 )
			{
				// draw title
				float x_center = (bounds.Left + bounds.Right)/2.0f;
				float y_center = (bounds.Top + bounds.Bottom)/2.0f;
				Font scaled_font;
				if (this.AutoScaleTitle)
				{
					scaled_font = Utils.ScaleFont( titleFont_, scale );
				}
				else
				{
					scaled_font = titleFont_;
				}
				g.DrawString( title_, scaled_font, this.titleBrush_, new PointF(x_center,y_center), titleDrawFormat_ );

				return;
			}

			// determine the [non physical] axes to draw based on the axis properties set.
			Axis xAxis1 = null;
			Axis xAxis2 = null;
			Axis yAxis1 = null;
			Axis yAxis2 = null;
			this.DetermineAxesToDraw( out xAxis1, out xAxis2, out yAxis1, out yAxis2 );

			// apply scale factor to axes as desired.

			if (xAxis1.AutoScaleTicks) 
				xAxis1.TickScale = scale;
			if (xAxis1.AutoScaleText)
				xAxis1.FontScale = scale;
			if (yAxis1.AutoScaleTicks)
				yAxis1.TickScale = scale;
			if (yAxis1.AutoScaleText)
				yAxis1.FontScale = scale;
			if (xAxis2.AutoScaleTicks)
				xAxis2.TickScale = scale;
			if (xAxis2.AutoScaleText)
				xAxis2.FontScale = scale;
			if (yAxis2.AutoScaleTicks)
				yAxis2.TickScale = scale;
			if (yAxis2.AutoScaleText)
				yAxis2.FontScale = scale;

			// determine the default physical positioning of those axes.
			PhysicalAxis pXAxis1 = null;
			PhysicalAxis pYAxis1 = null;
			PhysicalAxis pXAxis2 = null;
			PhysicalAxis pYAxis2 = null;
			this.DeterminePhysicalAxesToDraw( 
				bounds, xAxis1, xAxis2, yAxis1, yAxis2,
				out pXAxis1, out pXAxis2, out pYAxis1, out pYAxis2 );

			float oldXAxis2Height = pXAxis2.PhysicalMin.Y;

			// Apply axes constraints
			for (int i=0; i<axesConstraints_.Count; ++i)
			{
				((AxesConstraint)axesConstraints_[i]).ApplyConstraint( 
					pXAxis1, pYAxis1, pXAxis2, pYAxis2 );
			}

			/////////////////////////////////////////////////////////////////////////
			// draw legend if have one.
			// Note: this will update axes if necessary. 

			Point legendPosition = new Point(0,0);
			if (this.legend_ != null)
			{
				legend_.UpdateAxesPositions( 
					pXAxis1, pYAxis1, pXAxis2, pYAxis2,
					this.drawables_, scale, this.padding_, bounds, 
					out legendPosition );
			}

			float newXAxis2Height = pXAxis2.PhysicalMin.Y;

			float titleExtraOffset = oldXAxis2Height - newXAxis2Height;
	
			// now we are ready to define the bounding box for the plot area (to use in clipping
			// operations.
			plotAreaBoundingBoxCache_ = new Rectangle( 
				Math.Min( pXAxis1.PhysicalMin.X, pXAxis1.PhysicalMax.X ),
				Math.Min( pYAxis1.PhysicalMax.Y, pYAxis1.PhysicalMin.Y ),
				Math.Abs( pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X + 1 ),
				Math.Abs( pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y + 1 )
			);
			bbXAxis1Cache_ = pXAxis1.GetBoundingBox();
			bbXAxis2Cache_ = pXAxis2.GetBoundingBox();
			bbYAxis1Cache_ = pYAxis1.GetBoundingBox();
			bbYAxis2Cache_ = pYAxis2.GetBoundingBox();

			// Fill in the background. 
			if ( this.plotBackColor_ != null )
			{
				g.FillRectangle(
					new System.Drawing.SolidBrush( (Color)this.plotBackColor_ ),
					(Rectangle)plotAreaBoundingBoxCache_ );
			}
			else if (this.plotBackBrush_ != null)
			{
				g.FillRectangle( 
					this.plotBackBrush_.Get( (Rectangle)plotAreaBoundingBoxCache_ ),
					(Rectangle)plotAreaBoundingBoxCache_ );
			}
			else if (this.plotBackImage_ != null)
			{
				g.DrawImage( 
					Utils.TiledImage( this.plotBackImage_ , new Size( 
						((Rectangle)plotAreaBoundingBoxCache_).Width,
						((Rectangle)plotAreaBoundingBoxCache_).Height ) ), 
					(Rectangle)plotAreaBoundingBoxCache_ );
			}

			// draw title
			float xt = (pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X)/2.0f;
			float yt = bounds.Top + this.padding_ - titleExtraOffset;
			Font scaledFont;
			if (this.AutoScaleTitle)
			{
				scaledFont = Utils.ScaleFont( titleFont_, scale );
			}
			else
			{
				scaledFont = titleFont_;
			}
			g.DrawString( title_, scaledFont, this.titleBrush_,	new PointF(xt,yt), titleDrawFormat_ );
			
			//count number of new lines in title.
			int nlCount = 0;
			for (int i=0; i<title_.Length; ++i)
			{
				if (title_[i] == '\n')
					nlCount += 1;
			}

			SizeF s = g.MeasureString(title_,scaledFont);
			bbTitleCache_ = new Rectangle( (int)(xt-s.Width/2), (int)(yt), (int)(s.Width), (int)(s.Height)*(nlCount+1) );

			// draw drawables..
			System.Drawing.Drawing2D.SmoothingMode smoothSave = g.SmoothingMode;

			g.SmoothingMode = this.smoothingMode_;

			bool legendDrawn = false;

			for ( int i_o = 0; i_o < ordering_.Count; ++i_o )
			{
	
                int i = (int)ordering_.GetByIndex(i_o);
				double zOrder = (double)ordering_.GetKey( i_o );
				if (zOrder > this.legendZOrder_)
				{
					// draw legend.
					if ( !legendDrawn && this.legend_ != null )
					{
						legend_.Draw( g, legendPosition, this.drawables_, scale );
						legendDrawn = true;
					}
				}

                IDrawable drawable = (IDrawable)drawables_[i];
				XAxisPosition xap = (XAxisPosition)xAxisPositions_[i];
				YAxisPosition yap = (YAxisPosition)yAxisPositions_[i];

				PhysicalAxis drawXAxis;
				PhysicalAxis drawYAxis;

				if ( xap == XAxisPosition.Bottom )
				{
					drawXAxis = pXAxis1;
				}
				else
				{
					drawXAxis = pXAxis2;
				}

				if ( yap == YAxisPosition.Left )
				{
					drawYAxis = pYAxis1;
				}
				else
				{
					drawYAxis = pYAxis2;
				}
	
				// set the clipping region.. (necessary for zoom)
				g.Clip = new Region((Rectangle)plotAreaBoundingBoxCache_);
				// plot.
				drawable.Draw( g, drawXAxis, drawYAxis );
				// reset it..
				g.ResetClip();
			}
			
			if ( !legendDrawn && this.legend_ != null )
			{
				legend_.Draw( g, legendPosition, this.drawables_, scale );
			}

			// cache the physical axes we used on this draw;
			this.pXAxis1Cache_ = pXAxis1;
			this.pYAxis1Cache_ = pYAxis1;
			this.pXAxis2Cache_ = pXAxis2;
			this.pYAxis2Cache_ = pYAxis2;

			g.SmoothingMode = smoothSave;

			// now draw axes.
			Rectangle axisBounds;
			pXAxis1.Draw( g, out axisBounds );
			pXAxis2.Draw( g, out axisBounds );
			pYAxis1.Draw( g, out axisBounds );
			pYAxis2.Draw( g, out axisBounds );

#if DEBUG_BOUNDING_BOXES
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis1Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbXAxis2Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis1Cache_ );
			g.DrawRectangle( new Pen(Color.Orange), (Rectangle) bbYAxis2Cache_ );
			g.DrawRectangle( new Pen(Color.Red,5.0F),(Rectangle) plotAreaBoundingBoxCache_);
			//if(this.ShowLegend)g.DrawRectangle( new Pen(Color.Chocolate, 3.0F), (Rectangle) bbLegendCache_);
			g.DrawRectangle( new Pen(Color.DeepPink,2.0F), (Rectangle) bbTitleCache_);
#endif

		}


		/// <summary>
		/// Clears the plot and resets all state to the default.
		/// </summary>
		public void Clear()
		{
			Init();
		}


		/// <summary>
		/// Legend to use. If this property is null [default], then the plot
		/// surface will have no corresponding legend.
		/// </summary>
		public NPlot.Legend Legend
		{
			get
			{
				return this.legend_;
			}
			set
			{
				this.legend_ = value;
			}
		}


		/// <summary>
		/// Add an axis constraint to the plot surface. Axes constraints give you 
		/// control over where NPlot positions each axes, and the world - pixel
		/// ratio.
		/// </summary>
		/// <param name="constraint">The axis constraint to add.</param>
		public void AddAxesConstraint( AxesConstraint constraint )
		{
			this.axesConstraints_.Add( constraint );
		}


		/// <summary>
		/// Whether or not the title will be scaled according to size of the plot surface.
		/// </summary>
		public bool AutoScaleTitle
		{
			get
			{
				return autoScaleTitle_;
			}
			set
			{
				autoScaleTitle_ = value;
			}
		}


		/// <summary>
		/// When plots are added to the plot surface, the axes they are attached to
		/// are immediately modified to reflect data of the plot. If 
		/// AutoScaleAutoGeneratedAxes is true when a plot is added, the axes will
		/// be turned in to auto scaling ones if they are not already [tick marks,
		/// tick text and label size scaled to size of plot surface]. If false,
		/// axes will not be autoscaling.
		/// </summary>
		public bool AutoScaleAutoGeneratedAxes
		{
			get
			{
				return autoScaleAutoGeneratedAxes_;
			}
			set
			{
				autoScaleAutoGeneratedAxes_ = value;
			}
		}


		/// <summary>
		/// Remove a drawable object. 
		/// Note that axes are not updated.
		/// </summary>
		/// <param name="p">Drawable to remove.</param>
		/// <param name="updateAxes">if true, the axes are updated.</param>
		public void Remove( IDrawable p, bool updateAxes ) 
		{
			int index = drawables_.IndexOf( p );
			if (index < 0)
				return;
			drawables_.RemoveAt( index );
			xAxisPositions_.RemoveAt( index );
			yAxisPositions_.RemoveAt( index );
            zPositions_.RemoveAt(index);

            if (updateAxes)
            {
                this.UpdateAxes(true);
            }

            this.RefreshZOrdering();
        }


		/// <summary>
		/// If a plot is removed, then the ordering_ list needs to be 
		/// recalculated. 
		/// </summary>
		private void RefreshZOrdering() 
		{
			uniqueCounter_ = 0;
			ordering_ = new SortedList();
			for (int i = 0; i < zPositions_.Count; ++i) 
			{
				double zpos = Convert.ToDouble(zPositions_[i]);
				double fraction = (double)(++uniqueCounter_) / 10000000.0f;
				double d = zpos + fraction;
				ordering_.Add(d, i);
			}
		}



        /// <summary>
        /// Gets an array list containing all drawables currently added to the PlotSurface2D.
        /// </summary>
        public ArrayList Drawables
        {
			get
			{
				return this.drawables_;
			}
		}


		/// <summary>
		/// Returns the x-axis associated with a given plot.
		/// </summary>
		/// <param name="plot">the plot to get associated x-axis.</param>
		/// <returns>the axis associated with the plot.</returns>
		public Axis WhichXAxis( IPlot plot )
		{
			int index = drawables_.IndexOf( plot );
			XAxisPosition p = (XAxisPosition)xAxisPositions_[index];
			if ( p == XAxisPosition.Bottom )
				return this.xAxis1_;
			else
				return this.xAxis2_;
		}


		/// <summary>
		/// Returns the y-axis associated with a given plot.
		/// </summary>
		/// <param name="plot">the plot to get associated y-axis.</param>
		/// <returns>the axis associated with the plot.</returns>
		public Axis WhichYAxis( IPlot plot )
		{
			int index = drawables_.IndexOf( plot );
			YAxisPosition p = (YAxisPosition)yAxisPositions_[index];
			if ( p == YAxisPosition.Left )
				return this.yAxis1_;
			else
				return this.yAxis2_;
		}


		/// <summary>
		/// Setting this value determines the order (relative to IDrawables added to the plot surface)
		/// that the legend is drawn.
		/// </summary>
		public int LegendZOrder
		{
			get
			{
				return legendZOrder_;
			}
			set
			{
				legendZOrder_ = value;
			}
		}
		int legendZOrder_ = -1;


    } 
} 


